/*
    Speedbump aka Project Kimbula
    Copyright (C) 2010  Ping Shin Ching

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see http://www.gnu.org/licenses.

	Ping Shin Ching
	righteous.ninja@gmail.com OR ping_sc@hotmail.com
	More information at http://www.tidytutorials.com/p/speedbump.html
 */

#include <fcntl.h>

#include <pthread.h>
#include <unistd.h>
#include <sys/wait.h>

#include <signal.h>

#include <sys/socket.h>
#include <resolv.h>
#include <arpa/inet.h>
#include <string.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>

#ifdef DEBUG
#include <mcheck.h>
#endif

#include "init.h"
#include "preproxy.h"
#include "rules.h"
#include "protocolmanager.h"
#include "cpipes.h"
#include "rules.h"
#include "application.h"

#include "../filters/connlimsim.h"
#include "../filters/limburstsim.h"
#include "../filters/ratelimiter.h"

#include "../protocols/http/protocolhttphandler.h"
//#include "inmemorycache.h"

volatile short child_quit = 0;
volatile short parent_quit = 0;
ProtocolManager protocolmanager;

/*************************************************************************
 * The Signal handler
 *************************************************************************/
void signalhandler(int signal){

	logit(LOG_CRITICAL)<<"Received signal "<< signal <<" from "<< getpid()<<lend;
	
	switch(signal){
		case SIGHUP:
			child_quit=1;
			break;
		case SIGTERM:
		case SIGINT:
			child_quit=1;
			parent_quit=1;
			break;
		case SIGUSR2:
			/*Use this to dump filter data*/				
			break;

		default:
			raise(signal);
			break;
	}
}


/*************************************************************************
 * MAIN
 *************************************************************************/
int main(int argc, char* argv[]){
	FILE* pidfile;
	int i;
	char* c;
	InitParameters * initp;
	ProxyData*  ppd;
	int errorcount;

	//Setup signal handlers
	/*
	struct sigaction sa;
	sa.sa_handler=signalhandler;
	sa.sa_flags = 0;
	*/
	struct sigaction sa2;
	sa2.sa_handler=signalhandler;
	sa2.sa_flags = 0;
	sigemptyset(&sa2.sa_mask);

	struct sigaction blockedsa;
	blockedsa.sa_handler=SIG_IGN;
	blockedsa.sa_flags = 0;
	sigemptyset(&blockedsa.sa_mask);


	if(sigaction(SIGUSR1, &sa2, 0)==-1 || 
		sigaction(SIGUSR2, &sa2, 0)==-1 || 
		sigaction(SIGINT, &sa2, 0)==-1|| 
		sigaction(SIGHUP, &sa2, 0)==-1|| 
		sigaction(SIGTERM, &sa2, 0)==-1 ){
		perror("Failed to set signal handler(s) for SIGUSR[12], SIGTERM, SIGINT");
	}
	if(sigaction(SIGCHLD, &blockedsa, 0)==-1 || sigaction(SIGPIPE, &blockedsa, 0)==-1){
		perror("Failed to block signal SIGCHLD, SIGPIPE");
	}

	switch(getopt(argc, argv, "f:")){
		case 'f':
			c = optarg;	
			break;

		default:
			c = "preproxy.ini";	
			break;
	}

	errorcount=0;
	while(!parent_quit && errorcount++<5){
		child_quit=0;
		
#ifdef DEBUG
		mtrace();
#endif
		
		initp = new InitParameters();
		
		ppd =  (ProxyData*)malloc(sizeof(ProxyData));
		ppd->bindcounter = 0;

		if(initp->ReadInitFile(c)==-1){
			perror("Could not load default preproxy.ini file\n");
			free(ppd);
			return -1;
		}
	
		proxylog.Setuplogger(atoi((const char*)initp->GetInitParameter("loglevel")), initp->GetInitParameter("logfile"));
		proxylog.SetupAccesslogger("hello");	
		ppd->max_connections	= atoi((const char*)initp->GetInitParameter("maxconnections"));
		ppd->rate_limit_up	= atol((const char*)initp->GetInitParameter("ratelimit-up"));
		ppd->rate_limit_down	= atol((const char*)initp->GetInitParameter("ratelimit-down"));
		ppd->rate_adjustment	= atol((const char*)initp->GetInitParameter("rate-adjustment"));
	
		if((pidfile=fopen(initp->GetInitParameter("pidfile"),"w"))!=0){
			fprintf(pidfile, "%d", getpid());
			logf<<"Parent pid:"<<getpid()<<lend;
			fclose(pidfile);
		}
		
	
		//Intialize the filter container
		FilterChain *filterChain = new FilterChain(atoi((const char*)initp->GetInitParameter("filterlogging")));
		if(	
			filterChain->AddFilters(new RateLimitFilter(), initp)== -1 ||
			filterChain->AddFilters(new LimitBurstFilter(), initp)== -1 || 
			/*This should always be the last filter because it doesn't decay over time*/
			filterChain->AddFilters(new ConnLimitFilter(), initp)== -1 
		){
			loge<<"Filters didn't initialize properly"<<lend;
			return -1;
		}
	
		//Display some stuff
		initp->DisplayInitParameters();
	
	        InitParameters* sections;
	        for(i=0; (sections=initp->GetSectionByIndex(i))!=0 ; i++){
	                if(strcmp(sections->GetInitParameter("proxy-type"), "http")==0){
	                        protocolmanager.RegisterProtocolFactory(new ProtocolHTTPHandlerFactory(),
	                                atoi(sections->GetInitParameter("listen-port")),
	                                sections);
	                }
	                AddProxy(ppd, atoi((const char*)sections->GetInitParameter("listen-port")),
				atoi((const char*)sections->GetInitParameter("backlog")));
		}
	
		delete initp;
	
		proxylog.Printlogdetails();
	
#ifdef THREADXX
		printf("Using threaded (pthread) model\n");
#else
		printf("Using forked model\n");
#endif
	
		InitializeSocketSupport(1);
	
		//Start the main process
		ProxyCoreStart(ppd, filterChain);
	
		filterChain->FilterFree();
		delete filterChain;
	
		DestroyProxyData(ppd);

		if(!parent_quit){
			loge<<"Server is Refreshing\n"<<lend;
		}
		else{
			loge<<"Server is shutting down\n"<<lend;
		}

		//Close Logger
		proxylog.Closelogger();
	
		sleep(2);
	
	}
#ifdef DEBUG
		muntrace();
#endif				
	
}


/*************************************************************************
 * Start the proxy core with the main accept
 *************************************************************************/
void ProxyCoreStart(ProxyData* ppd, FilterChain* filterChain){

	int i, j, k;

	fd_set	rFd, rFdx;
	fd_set	eFd, eFdx;
	fd_set	wFd, wFdx;

	struct sockaddr_storage sadr;
	socklen_t addr_size = sizeof(struct sockaddr_storage);

	SOCKET	csock;

	PIPES* thispipe;

	short error_count;

	struct timeval stimeout;
	stimeout.tv_sec		= 60 ;
	stimeout.tv_usec	= 0 ;

	int	retv; 
	char*	retc;

	int childpid;

	ProxyTransfer* pt;
	
	InitializeSocketSupport(0);

	ppd->max_fd=0;
	
	for(i = 0 ; i < ppd->bindcounter; i++){
		printf("Bind counter %d\n",i);
		PrintProxy(&ppd->pcp[i]);	
		InitSocket(&ppd->pcp[i].ssock, SOCK_SERVER);
		ConnectSocket(&ppd->pcp[i].ssock, 0, ppd->pcp[i].listen_port, SOCK_SERVER, ppd->pcp[i].backlog);	
		if(ppd->pcp[i].ssock > ppd->max_fd)
			ppd->max_fd = ppd->pcp[i].ssock;
	}

	FD_ZERO(&rFd);
	FD_ZERO(&eFd);
	FD_ZERO(&wFd);

	//Setup the pipes
	IPCommunicationPipes* commpipes = new IPCommunicationPipes(ppd->max_connections, filterChain);

	int nooffdstoread = 0;

	error_count=0;
	while(!child_quit){
		logd<<"Free cons "<<commpipes->GetFreePipes()<<lend;

		//commpipes->reapZombiePipes();
		//Prepare the pipes for read
		commpipes->PrepareForRead(&rFd, &eFd);

		if((retv=commpipes->GetFreePipes())!=0){
			for(i=0;i<ppd->bindcounter;i++){
				FD_SET(ppd->pcp[i].ssock, &rFd);
				FD_SET(ppd->pcp[i].ssock, &eFd);
			}
		}
		else{
			for(i=0;i<ppd->bindcounter;i++){
				FD_CLR(ppd->pcp[i].ssock, &rFd);
				FD_CLR(ppd->pcp[i].ssock, &eFd);
			}
		}

		memcpy(&rFdx, &rFd, sizeof(rFd));
		memcpy(&wFdx, &wFd, sizeof(wFd));
		memcpy(&eFdx, &eFd, sizeof(eFd));


		if( (retv = select((ppd->max_fd >commpipes->GetMaxFd()?ppd->max_fd:commpipes->GetMaxFd())+1, &rFdx, &wFdx, &eFdx, 0) ) > 0){

			nooffdstoread = SelectPortsToRead(ppd, commpipes->GetFreePipes(), &rFdx);

			for(j = 0 ;  !child_quit && j<nooffdstoread ; j++){

				i = ppd->toread[j];
				ppd->pcp[i].lastaccesstime=time(0);

			
				if((csock = accept(ppd->pcp[i].ssock, (struct sockaddr*)&sadr, &addr_size)) == -1){
					loge<<"Core - Invalid socket accept "<<errno<<lend;
					error_count++;
					continue;
				}


				if(!(pt = (ProxyTransfer*)malloc(sizeof(ProxyTransfer)))){
					CloseSocket(&csock);
					continue;
				}
				else{
					pt->sp=0;
					pt->phi=0;
				}

				//Create the params to SP to be send to the child
				if((pt->sp = new ProtocolHandlerParams())){
					ipaddress ipad;
					switch(sadr.ss_family){
						case AF_INET:
							inet_ntop(AF_INET,   &(((struct sockaddr_in*)&sadr)->sin_addr), ipad.ipv4, INET_ADDRSTRLEN);
							ipad.iptype= AF_INET;
							break;
						case AF_INET6:
							inet_ntop(AF_INET6,  &(((struct sockaddr_in6*)&sadr)->sin6_addr), ipad.ipv6, INET6_ADDRSTRLEN);
							ipad.iptype=AF_INET6;
							break;
						case AF_UNSPEC:
							CloseSocket(&csock);
							FreeProxyTransfer(pt);	
							continue;
						break;

					}
					
					memcpy(&(pt->sp->remoteaddr), &ipad, sizeof(ipaddress));
					pt->sp->csock		= csock;
					pt->sp->ratelimitup		= ppd->rate_limit_up;
					pt->sp->ratelimitdown	= ppd->rate_limit_down;
					pt->sp->param1		= 0;
					pt->sp->redirecturl		= 0;
					pt->sp->rateadjustment	= ppd->rate_adjustment;
				}
				else{
					logit(LOG_CRITICAL)<<"Not enough memory to allocate sockparams "<<errno<<lend;
					CloseSocket(&csock);
					continue;
				}
					
				if(filterChain->PassFilters(FILTER_START, flattenip(pt->sp->remoteaddr), (void*)pt->sp)==0){
					if((retc=filterChain->GetFinalFilterRedirect())==0){
						CloseSocket(&csock);
						FreeProxyTransfer(pt);	
						continue;
					}
					else{
						pt->sp->redirecturl = retc;
					}
				}

				if((thispipe = commpipes->GetPipe(&rFd, &(pt->sp->remoteaddr) ))){
				
					logflush();

					if(!(pt->phi = protocolmanager.GetProtocolHandler(ppd->pcp[i].listen_port, pt->sp))){
						CloseSocket(&csock);
						close(thispipe->pipes[1]);
						close(thispipe->pipes[0]);
						FreeProxyTransfer(pt);
						loge<<"No protocol handler registered with port"<<ppd->pcp[i].listen_port<<lend;
						continue;
					}

#ifdef THREADXX	/*Threaded model using pthreads*/
					pt->sp->pid		= 0;
					pt->sp->commpipe	= thispipe->pipes[1];
					//pthread_t tid;

					//if(pthread_create(&thispipe->thread_id, 0, ProcessRunner, pt)==0 && pthread_detach(tid)!=0){
					if(pthread_create(&thispipe->thread_id, 0, ProcessRunner, pt)!=0){
						CloseSocket(&csock);
						close(thispipe->pipes[1]);
						close(thispipe->pipes[0]);
						FreeProxyTransfer(pt);
						logit(LOG_CRITICAL)<<"Core - thread create/detach failed "<< errno<<lend;
						continue;
					}

#else	/*Forked model using fork()*/
					switch( childpid=fork()){
						case -1:
							CloseSocket(&csock);
							close(thispipe->pipes[1]);
							close(thispipe->pipes[0]);
							FreeProxyTransfer(pt);
							logit(LOG_CRITICAL)<<"Core - forking failed "<< errno<<lend;	
							break;
					
						case 0:/*child*/ 
							
							close(thispipe->pipes[0]);
							
							for(k = 0 ; k < ppd->bindcounter; k++)
								CloseSocket(&ppd->pcp[k].ssock);

							DestroyProxyData(ppd);
#ifdef DEBUG
							muntrace();
#endif				
							pt->sp->pid		= getpid();
							pt->sp->commpipe	= thispipe->pipes[1];

							/*The main process*/
							ProcessRunner(pt);

							/* Remove the filters */
							filterChain->FilterFree();
							delete filterChain;

							child_quit = 1;
							delete commpipes;
							proxylog.Closelogger();
							
							exit(0);
							

						default: //server
							FreeProxyTransfer(pt);
							close(thispipe->pipes[1]);
							CloseSocket(&csock);
							break;
					}
#endif
				}//thispipe
				else{
					
					FreeProxyTransfer(pt);
					CloseSocket(&csock);
					continue;
				}

			}//for loop

			for(i=0;i<ppd->bindcounter;i++){
				if(FD_ISSET(ppd->pcp[i].ssock, &eFdx)){
					logit(LOG_CRITICAL)<<"Core - Listen client socket eFd "<<errno<<lend;
					continue;
				}
			}

			commpipes->ReadFromThePipes(&rFdx, &eFdx);

		}
		else{
			if(retv==0){
				loge<<"Select timeout"<<lend;
			}
			else if (errno == EINTR){
				loge<<"signal  EINTR received"<<lend;
				if(!child_quit)
					continue;
			}
			else{
				perror("Core select error\n");
				logit(LOG_CRITICAL)<<"Core - select error "<<i<<"  "<<errno<<lend;
				child_quit=1;
			}
		}

		if(error_count > 10)
			child_quit=0;
			
	}//main while loop


	for(i = 0 ; i < ppd->bindcounter; i++){
		CloseSocket(&ppd->pcp[i].ssock);

	}

	delete commpipes;

	logf<<"quit"<<lend;
}


/*************************************************************************
 * The void*(*)(void*)process. 
 *************************************************************************/
void* ProcessRunner(void* data){
	ProxyTransfer *pt = (ProxyTransfer*)data;

	pt->phi->Run(pt->sp);

	FreeProxyTransfer(pt);

	return 0;
}


/*************************************************************************
 * Remove the proxytransfer data 
 *************************************************************************/
void FreeProxyTransfer(ProxyTransfer* pt){
	if(!pt)
		return;
	if(pt->phi){
		delete pt->phi;
		pt->phi = 0;
	}
	if(pt->sp){
		delete pt->sp;
		pt->sp = 0;
	}
	free(pt);

}


/*************************************************************************
 * Function to decide what ports to read. Stops greedy ports from getting
 * all the accepts
 *************************************************************************/
int SelectPortsToRead(ProxyData* ppd, int slots, fd_set* prFdx){
	int i,j;
	int oldesttime=time(0);
	int oldesttimeindex;
	int counter=0;
	slots = slots<ppd->bindcounter ?slots : ppd->bindcounter;
	
	for(j = 0 ; j < slots ;j ++){
		oldesttimeindex=-1;
		for(i = 0 ; i < ppd->bindcounter ; i++){
			if(ppd->pcp[i].lastaccesstime !=-1 && FD_ISSET(ppd->pcp[i].ssock, prFdx) ){
				if(ppd->pcp[i].lastaccesstime <= oldesttime){
					oldesttime = ppd->pcp[i].lastaccesstime;
					oldesttimeindex = i;
				}
			}
		}
		
		if(oldesttimeindex!=-1){
			ppd->toread[counter]=oldesttimeindex;
			ppd->pcp[oldesttimeindex].lastaccesstime = -1;
			counter++;
		}
	}
	return counter;
}


/*************************************************************************
 * Initialize the proxy data
 *************************************************************************/
void InitProxyData(ProxyData* ppd){
	int i;
	for(i = 0 ; i < MAX_SERVER_BINDS ; i++){
		ppd->pcp[i].ssock=-1;
		ppd->toread[i] = 0;
	}
	ppd->bindcounter = 0;
}


/*************************************************************************
 * Add the proxy data
 *************************************************************************/
int AddProxy(ProxyData* ppd,
			unsigned int listen_port,
			unsigned int backlog){

	if(ppd->bindcounter+1 > MAX_SERVER_BINDS ){
		return -1;
	}
	else{
		ppd->pcp[ppd->bindcounter].backlog = backlog;
		ppd->pcp[ppd->bindcounter].listen_port = listen_port;
		ppd->pcp[ppd->bindcounter].lastaccesstime=0;
		ppd->bindcounter++;
		return 0;
	}
	
}


/*************************************************************************
 * Destroy the proxy data
 *************************************************************************/
void DestroyProxyData(ProxyData* ppd){
	int i;
	if(ppd){
		for(i = 0 ; i < ppd->bindcounter ; i++ )
			CloseSocket(&ppd->pcp[i].ssock);
		free(ppd);
		ppd=0;
	}
}


/*************************************************************************
 * Print the proxy data to the stdout
 *************************************************************************/
void PrintProxy(ProxyCoreParams* ppcp){
}
